package net.pulseproject.commons.misc.binder;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BindableDelegate<V, F extends FieldDescriptor<V>> implements Bindable<V, F> {
	
	private static final Log LOG = LogFactory.getLog(BindableDelegate.class);
	
	private final V valueHolder;
	
	private final Map<F, Collection<Binder<F, ? extends Object>>> binders =
		new HashMap<F, Collection<Binder<F, ? extends Object>>>();
	
	public BindableDelegate(final V valueHolder) {
		if(valueHolder == null) { throw new IllegalArgumentException("valueHolder == null"); }
		this.valueHolder = valueHolder;
	}

	@Override
	public final void addBinding(final Binder<F, ? extends Object> binder) {
		LOG.debug("addBinding(binder=" + binder + ")");
		if(binder == null) { throw new IllegalArgumentException("binder == null"); }
		
		final F field = binder.getField();
		
		if(this.binders.containsKey(field) == false) {
			this.binders.put(field, new HashSet<Binder<F, ? extends Object>>());
		}
		
		final Collection<Binder<F, ? extends Object>> storedBinders = this.binders.get(field);
		storedBinders.add(binder);
		
		// dispatch for first time to init value
		this.dispatchBindingEvent(field);
	}

	@Override
	public final void removeBinding(final Binder<F, ? extends Object> binder) {
		LOG.debug("removeBinding(binder=" + binder + ")");
		if(binder == null) { throw new IllegalArgumentException("binder == null"); }
		
		final F field = binder.getField();
		final Collection<Binder<F, ? extends Object>> storedBinders = this.binders.get(field);
		final boolean wasRemoved = storedBinders.remove(binder);
		if(wasRemoved == false) {
			throw new RuntimeException("Could not remove binder as it was not yet registered: " + binder);
		}
	}

	public final void dispatchBindingEvent(final F field) {
		LOG.debug("dispatchBindingEvent(valueHolder, field=" + field + ")");
		final Collection<Binder<F, ? extends Object>> storedBinders = this.binders.get(field);
		if(storedBinders == null) {
			LOG.debug("Aborting, as no binders registered.");
			return;
		}
		
		final Object value = field.getValueOf(this.valueHolder);
		for (final Binder<F, ? extends Object> binder : storedBinders) {
			binder.onValueSet(value);
		}
	}

}
